/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Camera.h"
#include "Root.h"
#include <xml/Stream.h>
#include <xml/Saver.h>
#include <safe_new.h>
#include <Converter.h>
#include <string>
#include <lib3d/hard/Hardware.h>
#include <lib3d/Math_lib3d.h>
#include <logs/Log.h>

#include <boost/bind.hpp>

//#define SPEED_TEST

namespace liba
{
namespace lib3d
{
namespace node
{

const Atom Camera::near_plane_name( "NearPlane" );
const Atom Camera::far_plane_name( "FarPlane" );
const Atom Camera::fog_color_name( "FogColor" );
const Atom Camera::fog_range_name( "FogRange" );

Camera * Camera::active_rendering_camera = NULL;
	
Camera::Camera(const Atom & name, Node * parent )
:	Node( name, parent )
{
	GetRoot()->add_camera( this );
	this->RegisterProperty( near_plane_name, &this->desc.near_plane );
	this->RegisterProperty( far_plane_name, &this->desc.far_plane );
	this->RegisterProperty( fog_range_name, &this->desc.fog_range );

	this->desc.near_plane.Changed().connect( boost::bind( &Camera::OnChangeParameters<double>, this, _1 ) );
	this->desc.far_plane.Changed().connect( boost::bind( &Camera::OnChangeParameters<double>, this, _1 ) );
	this->desc.fog_range.Changed().connect( boost::bind( &Camera::OnChangeParameters<double>, this, _1 ) );
}

Camera::~Camera()
{
	if(active_rendering_camera == this)
		active_rendering_camera = NULL;
	
	GetRoot()->remove_camera( this );
}

bool Camera::on_attribute( xml::Provider * prov, const std::string & name, const std::string & value )
{
	if ( name == "NearPlane" )
	{
		double near_plane;
		bool res = converter::convert( value, &near_plane );
		if (res)
			this->desc.near_plane = near_plane;
		return res;
	}
	if ( name == "FarPlane" )
	{
		double far_plane;
		bool res = converter::convert( value, &far_plane );
		if (res)
			this->desc.far_plane = far_plane;
		return res;
	}
	if ( name == "FogRange" )
	{
		double fog_range;
		bool res = converter::convert( value, &fog_range );
		if (res)
			this->desc.fog_range = fog_range;
		return res;
	}
	if ( name == "FogColor" )
	{
		return converter::convert( value, &desc.fog_color );
	}
	if ( name == "ViewportLeft" )
	{
		return converter::convert( value, &desc.viewport.left );
	}
	if ( name == "ViewportRight" )
	{
		return converter::convert( value, &desc.viewport.right );
	}
	if ( name == "ViewportTop" )
	{
		return converter::convert( value, &desc.viewport.top );
	}
	if ( name == "ViewportBottom" )
	{
		return converter::convert( value, &desc.viewport.bottom );
	}
	return Node::on_attribute( prov, name, value );
}

void Camera::on_save_attributes( xml::SaverBase * saver )const
{
	if(desc.near_plane.Get() != 0)
		saver->save_attribute("NearPlane", desc.near_plane.Get());

	if(desc.far_plane.Get() != 0)
		saver->save_attribute("FarPlane", desc.far_plane.Get());

	if(desc.fog_range.Get() != 0)
		saver->save_attribute("FogRange", desc.fog_range.Get());

	if(desc.fog_color != Color(1,1,1))
		saver->save_attribute("FogColor", desc.fog_color);

	if(desc.viewport.left != -1)
		saver->save_attribute("ViewportLeft", desc.viewport.left);

	if(desc.viewport.right != 1)
		saver->save_attribute("ViewportRight", desc.viewport.right);

	if(desc.viewport.top != 1)
		saver->save_attribute("ViewportTop", desc.viewport.top);

	if(desc.viewport.bottom != -1)
		saver->save_attribute("ViewportBottom", desc.viewport.bottom);

	Node::on_save_attributes(saver);
}

bool Camera::on_end_node( xml::Provider * prov )
{
	on_change_parameters();
	return true;
}

const CameraDesc & Camera::get_desc()
{
	if(!target_name.empty() && !target)
	{
		target = parent->FindNode(Atom(target_name), true);
		if(!target)
			target_name.clear();
	}

	if(target)
	{
		Matrix mat = target->GetGlobalMatrix();
		ChangeRotation(mat.shift(), top_vector);
	}

	desc.world = GetGlobalMatrix();
	return desc;
}
	
Camera * Camera::active_camera()
{
	return active_rendering_camera;
}

bool Camera::start_render_to_list( hard::Hardware * hardware, hard::RenderList * render_list )
{
#ifdef SPEED_TEST
	__int64 now = threads::get_precise_tick_count();
#endif
	CameraDesc desc = get_desc();
	render_list->camera_world = desc.world;
	render_list->camera_projection = desc.projection;
	
	render_list->frustrum = desc.frustrum;
	
	render_list->viewport = desc.viewport;
	render_list->fog_color = desc.fog_color;
	render_list->fog_range = desc.fog_range;
	
	active_rendering_camera = this;
	
	GetRoot()->RenderToList( hardware, render_list );
#ifdef SPEED_TEST
	int passed = threads::get_precise_tick_count() - now;
	logs::log() << "\t\tCamera::start_render_to_list(" << get_name().get_str() << "):" << passed * threads::get_precise_tick_time()*1000 << " msec\n";
#endif
	return true;
}

void Camera::CreateProxy(ProxyVisitor * proxyVisitor)
{
	proxyVisitor->CreateCameraProxy(this);
}

} // namespace node
} // namespace lib3d
} // namespace liba

